#include "server.h"
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <stdio.h>
#include <fcntl.h>
#include <errno.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <assert.h>
#include <sys/sendfile.h>
#include <dirent.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <pthread.h>

typedef struct pthread_info {
	pthread_t tid;
	int fd;
	int ep_fd;
}p_info;

int init_listen_fd(unsigned short port)
{
	// 1.创建监听的fd
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if (fd == -1)
	{
		perror("socket error");
		return -1;
	}
	// 2.设置端口复用
	int opt = 1;
	int res = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	if (res == -1)
	{
		perror("setsocket error");
		return -1;
	}
	// 3.绑定ip和端口
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY;
	res = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
	if (res == -1)
	{
		perror("bind error");
		return -1;
	}
	// 4.设置监听
	res = listen(fd, 128);
	if (res == -1)
	{
		perror("listen error");
		return -1;
	}
	// 返回fd
	return fd;
}

int epoll_run(int fd)
{
	// 1.创建epoll实例
	int ep_fd = epoll_create(1);
	if (ep_fd == -1)
	{
		perror("epoll_create error");
		return -1;
	}

	// 2.把需要监听的文件描述符添加到epoll树上
	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = EPOLLIN;// | EPOLLET;
	int res = epoll_ctl(ep_fd, EPOLL_CTL_ADD, fd, &ev);
	if (res == -1)
	{
		perror("epoll_ctl error");
		return -1;
	}

	// 3.检测
	struct epoll_event evs[1024];
	int size = sizeof(evs) / sizeof(evs[0]);
	while (1)
	{
		int num = epoll_wait(ep_fd, evs, size, -1);
		for (int i = 0; i < num; i++)
		{
			p_info* info = (p_info*)malloc(sizeof(p_info));
			info->ep_fd = ep_fd;
			info->fd = evs[i].data.fd;
			if (info->fd == fd)
			{
				// 建立新连接 accept
				//accept_client(t_fd, ep_fd);

				//创建子线程来处理新连接
				pthread_create(&info->tid, NULL, accept_client, info);
				pthread_detach(&info->tid);
			}
			else
			{
				// 主要处理接收数据，由于没有监听写事件，所以不做判断
				//recv_http_request(t_fd, ep_fd);
				
				//创建子线程来处理通讯
				pthread_create(&info->tid, NULL, recv_http_request, info);
				pthread_detach(&info->tid);
				
			}
		}
	}

	return 0;
}

void* accept_client(void* arg)
{
	p_info* info = (p_info*)arg;

	// 1.建立连接
	int cfd = accept(info->fd, NULL, NULL);
	if (cfd == -1)
	{
		perror("accept error");
		free(info);
		return NULL;
	}

	// 2.设置文件描述符为非阻塞的
	int flag = fcntl(cfd, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(cfd, F_SETFL, flag);

	// 3.把文件描述符放到epoll树上检测
	struct epoll_event ev;
	ev.data.fd = cfd;
	ev.events = EPOLLIN | EPOLLET;	//这里使用边沿非阻塞模式效率更高
	int res = epoll_ctl(info->ep_fd, EPOLL_CTL_ADD, cfd, &ev);
	if (res == -1)
	{
		perror("epoll_ctl error");
	}
	free(info);
	return NULL;
}

void* recv_http_request(void* arg) 
{
	p_info* info = (p_info*)arg;

	//为了不覆盖信息，使用temp来接收数据再合到buff中
	char temp[1024] = { 0 };
	char buff[4096] = { 0 };
	int len = 0, totle = 0;
	while ((len = recv(info->fd, temp, sizeof(temp), 0)) > 0)
	{
		if (totle + len < sizeof(buff))
		{
			memcpy(buff + totle, temp, len);
		}
		totle += len;

		//如果有多的就抛弃，因为我们只需要读http请求行
	}

	//判断是否接收完毕
	if (len == -1 && errno == EAGAIN)
	{
		//对http的请求协议的解析，解析请求行
		char* pt = strstr(buff, "\r\n");
		int req_len = pt - buff;
		buff[req_len] = '\0';

		parse_request_line(buff, info->fd);
	}
	else if (len == 0)
	{
		//客户端断开连接。。
		epoll_ctl(info->ep_fd, EPOLL_CTL_DEL, info->fd, NULL);
	}
	else
	{
		perror("recv error");
	}
	close(info->fd);
	free(info);
	return NULL;
}

int parse_request_line(const char* line, int cfd)
{
	//解析请求行	get /xxx/1.jpg http/1.1
	char method[12];
	char path[1024];
	sscanf(line, "%[^ ] %[^ ]", method, path);
	
	printf("method : %s,path : %s\n", method, path);
	if (strcasecmp(method, "get") != 0)		//strcasecmp比较不区分大小写
	{
		return -1;
	}

	decode_msg(path, path);

	//处理客户端请求的静态资源(目录或者文件)
	char* file = NULL;
	if (strcmp(path, "/") == 0)
	{
		file = "./";
	}
	else
	{
		file = path + 1;
	}

	//获取文件属性
	struct stat st;
	if (stat(file, &st) == -1)
	{
		//文件不存在--回复404
		send_head_msg(cfd, 404, "Not Found", get_file_type(".html"), -1);
		send_file("404.html", cfd);
		return 0;
	} 

	//判断文件类型
	if (S_ISDIR(st.st_mode))
	{
		printf("send dir\n");
		//把这个目录发给客户端
		send_head_msg(cfd, 200, "OK", get_file_type(".html"), -1);
		send_dir(file, cfd);
		printf("send dir end\n");
	}
	else
	{
		printf("send file\n");
		//把文件的内容发给客户端
		send_head_msg(cfd, 200, "OK", get_file_type(file), st.st_size);
		send_file(file, cfd);
	}
	return 0;
}

int send_file(const char* file_name, int cfd)
{
	//1.打开文件
	int fd = open(file_name, O_RDONLY);
	assert(fd > 0);

#if 0
	while (1)
	{
		char buff[1024];
		int len = read(fd, buff, sizeof(buff));
		if (len > 0)
		{
			send(cfd, buff, sizeof(buff), 0);

			//发慢一点，以防另一端处理不过来
			usleep(10);
		}
		else if (len == 0)
		{
			break;
		}
		else
		{
			perror("read error");
		}
	}
#else
	//liunx提供了一个sendfile，
	//使用sendfile可以不用进行拷贝，有效降低代码复杂性，提高效率
	int size = lseek(fd, 0, SEEK_END);	//计算文件大小
	lseek(fd, 0, SEEK_SET);				//将文件重定向到开头
	off_t offset = 0;
	while (offset < size)
	{
		int res = sendfile(cfd, fd, &offset, size);
		printf("res = %d\n", res);
		if (res == -1)
		{
			perror("sendfile");
		}
	}
	
#endif
	close(fd);
	return 0;
}

int send_head_msg(int cfd, int status, const char* descr, const char* type, int length)
{
	//状态行
	char buff[4096] = { 0 };
	sprintf(buff, "http/1.1 %d %s\r\n", status, descr);

	//响应头
	sprintf(buff + strlen(buff), "content-type:%s\r\n", type);
	sprintf(buff + strlen(buff), "content-length:%d\r\n", length);

	//空行
	sprintf(buff + strlen(buff), "\r\n");

	printf("send head");
	send(cfd, buff, strlen(buff), 0);
	return 0;
}

const char* get_file_type(const char* file_name)
{
	//a,jpg a.mp4 a.html
	//自右向左查找"."字符，如不存在返回NULL
	const char* dot = strrchr(file_name, '.');
	if (dot == NULL)
		return "text/plain; charset=utf-8";	//纯文本
	if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
		return "text/html; charset=utf-8";
	if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
		return "image/jpeg";
	if (strcmp(dot, ".gif") == 0)
		return "image/gif";
	if (strcmp(dot, ".png") == 0)
		return "image/png";
	if (strcmp(dot, ".css") == 0)
		return "text/css";
	if (strcmp(dot, ".au") == 0)
		return "audio/basic";
	if (strcmp(dot, ".wav") == 0)
		return "audio/wav";
	if (strcmp(dot, ".avi") == 0)
		return "video/x-msvildeo";
	if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
		return "video/quicktime";
	if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
		return "video/mpeg";
	if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
		return "model/vrml";
	if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
		return "audio/midi";
	if (strcmp(dot, ".mp3") == 0)
		return "audio/mpeg";
	if (strcmp(dot, ".ogg") == 0)
		return "application/ogg";
	if (strcmp(dot, "pac") == 0)
		return "application/x-ns-proxy-autoconfig";
	return "text/plain; charset=utf-8";
}

int send_dir(const char* dir, int cfd)
{
	//读取到目录的内容使用html网页的方式发送给客户端
	char buff[4096] = { 0 };		//用来存储html内容
	sprintf(buff, "<html lang=en><head><title>%s</title></head><body><table>", dir);
	struct dirent** name_list;
	int num = scandir(dir, &name_list, NULL, alphasort);
	
	for (int i = 0; i < num; i++)
	{
		//取出文件名 name_list 指向的是一个指针数组
		printf("name = %s\n", name_list[i]->d_name);
		char* name = name_list[i]->d_name;

		//判断是否为目录
		struct stat st;
		char path[1024] = { 0 };
		sprintf(path, "%s/%s", dir, name);
		stat(path, &st);
		
		if (S_ISDIR(st.st_mode))
		{
			//添加目录信息和a标签	<a href="">name</a>
			sprintf(buff + strlen(buff),
				"<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
				name, name, st.st_size);
		}
		else
		{
			//添加文件信息和a标签	<a href="">name</a>
			sprintf(buff + strlen(buff),
				"<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
				name, name, st.st_size);
		}
		//发送
		send(cfd, buff, strlen(buff), 0);
		memset(buff, 0, sizeof(buff));
		free(name_list[i]);

		printf("name = %s end\n", name);
	}
	//将剩下的html标签拼接发送给客户端
	printf("send end");
	sprintf(buff, "</table></body></html>");
	send(cfd, buff, strlen(buff), 0);
	free(name_list);
	return 0;
}

int hex_to_dec(char c)
{
	if (c >= '0'&&c <= '9')
		return c - '0';
	if (c >= 'a'&&c <= 'f')
		return c - 'a' + 10;
	if (c >= 'A'&&c <= 'F')
		return c - 'A' + 10;
}

void decode_msg(char* to, char* from)
{
	for (; *from != '\0'; ++to, ++from)
	{
		// isxdigit -> 判断字符是否为十六进制格式，取值在0-f
		if (from[0] == '0'&&isxdigit(from[1]) && isxdigit(from[2]))
		{
			*to = hex_to_dec(from[1]) * 16 + hex_to_dec(from[2]);
			from += 2;
		}
		else
		{
			*to = *from;
		}
	}
}